package com.lqwu.mallchatserver.user.service.impl;

import cn.hutool.core.util.ObjUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.lqwu.mallchatserver.common.domain.enums.NormalOrNoEnum;
import com.lqwu.mallchatserver.common.domain.enums.YesOrNoEnums;
import com.lqwu.mallchatserver.common.event.UserBlackEvent;
import com.lqwu.mallchatserver.common.event.UserRegisterEvent;
import com.lqwu.mallchatserver.common.exception.BusinessException;
import com.lqwu.mallchatserver.common.exception.CommonErrorEnum;
import com.lqwu.mallchatserver.common.utils.AssertUtil;
import com.lqwu.mallchatserver.user.mapper.UserMapper;
import com.lqwu.mallchatserver.user.model.dto.ItemInfoDTO;
import com.lqwu.mallchatserver.user.model.dto.ItemInfoReq;
import com.lqwu.mallchatserver.user.model.dto.SummeryInfoDTO;
import com.lqwu.mallchatserver.user.model.dto.SummeryInfoReq;
import com.lqwu.mallchatserver.user.model.entry.*;
import com.lqwu.mallchatserver.user.model.enums.BlackTypeEnum;
import com.lqwu.mallchatserver.user.model.enums.ItemEnum;
import com.lqwu.mallchatserver.user.model.enums.ItemTypeEnum;
import com.lqwu.mallchatserver.user.model.enums.UserStatusEnum;
import com.lqwu.mallchatserver.user.model.vo.BadgeResp;
import com.lqwu.mallchatserver.user.model.vo.UserInfoResp;
import com.lqwu.mallchatserver.user.service.BlackService;
import com.lqwu.mallchatserver.user.service.ItemConfigService;
import com.lqwu.mallchatserver.user.service.UserBackpackService;
import com.lqwu.mallchatserver.user.service.UserService;
import com.lqwu.mallchatserver.user.service.cache.ItemCache;
import com.lqwu.mallchatserver.user.service.cache.UserCache;
import com.lqwu.mallchatserver.user.service.cache.UserSummaryCache;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author 伍立权
 * @description 针对表【user(用户表)】的数据库操作Service实现
 * @createDate 2025-05-25 17:38:24
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    private static final String MODIFY_NAME_CARD_LOCK = "MODIFY_NAME_CARD_LOCK"; // 定义为业务前缀

    @Resource
    private UserBackpackService userBackpackService;
    @Resource//编程式事务
    private TransactionTemplate transactionTemplate;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private ItemConfigService itemConfigService;
    @Resource
    private ItemCache itemCache;
    @Resource
    private ApplicationEventPublisher publisher;
    @Resource
    private BlackService blackService;
    @Resource
    private UserCache userCache;
    @Resource
    private UserSummaryCache userSummeryCache;

    /**
     * 注册
     *
     * @param registeredUser
     */
    @Override
    @Transactional
    public Long register(User registeredUser) {
        boolean save = this.save(registeredUser);
        AssertUtil.isTrue(save, "系统出小差了，注册错误<T-T>");
        // 用户注册的事件发布
        publisher.publishEvent(new UserRegisterEvent(this, registeredUser));
        return registeredUser.getId();
    }

    /**
     * 获取用户信息
     *
     * @param uid
     * @return
     */
    @Override
    public UserInfoResp getUserInfo(Long uid) {
        User user = this.getById(uid);
        UserInfoResp userInfoResp = new UserInfoResp();
        BeanUtils.copyProperties(user, userInfoResp);
        int count = getCount(uid);
        userInfoResp.setModifyNameChance(count);
        return userInfoResp;
    }

    /**
     * 根据用户id获取改名卡数量
     *
     * @param uid
     * @return
     */
    private int getCount(Long uid) {
        // 改名次数为背包中的未使用改名卡的个数
        LambdaQueryWrapper<UserBackpack> queryWrapper = new LambdaQueryWrapper<>();
        // 未使用改名卡
        queryWrapper.eq(UserBackpack::getUid, uid)
                .eq(UserBackpack::getItemId, ItemEnum.MODIFY_NAME_CARD.getId())
                .eq(UserBackpack::getStatus, YesOrNoEnums.NO.getStatus());
        return userBackpackService.count(queryWrapper);
    }

    /**
     * 修改用户昵称
     *
     * @param uid
     * @param name
     */
    @Override
    public void modifyName(Long uid, String name) throws InterruptedException {
        // 1.校验改名卡是否有
        int count = getCount(uid);
        AssertUtil.isTrue(count <= 0, "背包中无可用的改名卡，无法改名~亲~");
        // 2.判断昵称是否合规
        // 长度校验
        if (name.length() > 6) {
            throw new BusinessException(CommonErrorEnum.PARAM_VALID.getErrorCode(), "昵称长度过长，我记不住喔~~~");
        }
        // 是否存在重复
        User user = lambdaQuery().eq(User::getName, name).one();
        if (user != null) {
            throw new BusinessException("昵称已被抢占，换一个试试吧~~~");
        }
        // 分布式锁 + 事务
        RLock lock = redissonClient.getLock(MODIFY_NAME_CARD_LOCK + String.valueOf(uid).intern());
        try {
            // 竞争分布式锁，等待 3 秒，15 秒自动释放
            boolean res = lock.tryLock(3, 15, TimeUnit.SECONDS);
            if (res) {
                // 抢到锁的业务才能执行改名逻辑
                // 使用编程式事务，保证一致性
                transactionTemplate.execute(status -> {
                    // 3.更新用户昵称
                    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
                    queryWrapper.eq(User::getId, uid)
                            .eq(User::getName, name);
                    boolean update = this.update(queryWrapper);
                    AssertUtil.isTrue(update, "系统出小差了，更新错误<T-T>");
                    // 4.背包中改名卡用掉
                    LambdaQueryWrapper<UserBackpack> wrapper = new LambdaQueryWrapper<>();
                    wrapper.eq(UserBackpack::getUid, uid)
                            .eq(UserBackpack::getItemId, ItemEnum.MODIFY_NAME_CARD.getId())
                            .eq(UserBackpack::getStatus, YesOrNoEnums.NO.getStatus())
                            .orderByAsc(UserBackpack::getId)
                            .last("limit 1");
                    UserBackpack userBackpack = userBackpackService.getOne(wrapper);
                    userBackpack.setStatus(YesOrNoEnums.Yes.getStatus());
                    update = userBackpackService.updateById(userBackpack);
                    AssertUtil.isTrue(update, "系统出小差了，改名卡更新错误<T-T>");
                    return true;
                });
            }
        } finally {
            if (lock != null && lock.isLocked()) {
                if (lock.isHeldByCurrentThread()) {
                    // 记得释放锁
                    lock.unlock();
                }
            }
        }
    }

    /**
     * 用户徽章列表
     *
     * @param uid
     * @return
     */
    @Override
    public List<BadgeResp> badges(Long uid) {
        // 1.校验数据
        AssertUtil.isTrue(uid != null, "用户id为空");
        // 2.获取当前用户
        User user = this.getById(uid);
        AssertUtil.isTrue(user != null, "用户不存在");
        // 3.获取所有徽章列表
        List<ItemConfig> itemConfigList = itemCache.getByType(ItemTypeEnum.BADGE.getType());
        // 4.遍历，判断用户是否拥有
        List<BadgeResp> badgeRespList = new ArrayList<>();
        for (ItemConfig itemConfig : itemConfigList) {
            BadgeResp badgeResp = new BadgeResp();
            badgeResp.setId(itemConfig.getId());
            badgeResp.setImg(itemConfig.getImg());
            badgeResp.setDescribe(itemConfig.getDescribe());
            // 从背包点中获取
            UserBackpack userBackpack = userBackpackService.lambdaQuery()
                    .eq(UserBackpack::getItemId, itemConfig.getId())
                    .eq(UserBackpack::getUid, uid)
                    .one();
            // 判断是否拥有，和佩戴
            if (userBackpack != null) {
                badgeResp.setObtain(1);
            }
            badgeResp.setWearing(user.getItemId().equals(itemConfig.getId()) ? 1 : 0);
            badgeRespList.add(badgeResp);
        }
        // 将佩戴的徽章置顶, 先按佩戴排序
        badgeRespList.sort(
                Comparator.comparingInt(BadgeResp::getWearing).reversed()
                        .thenComparing(Comparator.comparingInt(BadgeResp::getObtain).reversed())
        );
        return badgeRespList;
    }

    /**
     * 佩戴徽章
     *
     * @param itemId
     * @param uid
     */
    @Override
    public void wearingBadge(Long itemId, Long uid) {
        // 1.校验徽章是否存在
        ItemConfig itemConfig = itemCache.getById(itemId);
        AssertUtil.isNotEmpty(itemConfig,  "徽章不存在，请不要乱传欧^o^");
        AssertUtil.isTrue(itemConfig.getType().equals(ItemTypeEnum.BADGE.getType()), "非徽章，不能佩戴欧^o^");
        // 2.校验用户是否有该徽章
        UserBackpack userBackpack = userBackpackService.lambdaQuery()
                .eq(UserBackpack::getItemId, itemId)
                .eq(UserBackpack::getUid, uid)
                .one();
        AssertUtil.isNotEmpty(userBackpack, "用户没有该徽章，无法佩戴");
        // 3.更新用户佩戴徽章
        User user = new User();
        user.setId(uid);
        user.setItemId(itemId);
        boolean update = this.updateById(user);
        AssertUtil.isTrue(update, "系统出小差了，佩戴徽章错误<T-T>");
    }

    /**
     * 拉黑
     * @param uid
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void black(Long uid) {
        // 1.校验数据,该用户是否存在
        User user = this.getById(uid);
        AssertUtil.isNotEmpty(user, "用户不存在，您要拉黑谁? <-_->");
        // 2.校验用户是否已拉黑
        boolean isBlack = user.getStatus().equals(UserStatusEnum.BLACK.getId());
        if (isBlack) return;
        // 3.构建 uid 黑名单对象
        Black blackUid = new Black();
        blackUid.setType(BlackTypeEnum.UID.getType());
        blackUid.setTarget(uid.toString());
        // 4.拉黑用户，uid 的方式
        boolean save = blackService.save(blackUid);
        AssertUtil.isTrue(save, "拉黑 uid 失败，请稍后再试 <-_->");
        // 5.拉黑用户，ip 的方式
        blackIP(user.getIpInfo());
        // 6.发送拉黑事件
        publisher.publishEvent(new UserBlackEvent(this, user));
    }

    /**
     * 获取好友列表(包含id、是否在线、名字、头像)
     * @param uids
     * @return
     */
    @Override
    public List<User> getFriendList(List<Long> uids) {
        return lambdaQuery().in(User::getId, uids)
                .select(User::getId,User::getActiveStatus,User::getName,User::getAvatar)
                .list();
    }

    /**
     * 获取用户聚合信息
     * @param req
     * @return
     */
    @Override
    public List<SummeryInfoDTO> getSummeryUserInfo(SummeryInfoReq req) {
        // 获取用户聚合信息请求集合（包含id、最后更新时间）
        List<SummeryInfoReq.infoReq> reqList = req.getReqList();
        // 1.获取前端需要同步的用户id
        List<Long> uids = getNeedSyncUserIdList(reqList);
        // 2.加载需要同步的用户聚合信息
        Map<Long, SummeryInfoDTO> batch = userSummeryCache.getBatch(uids);
        return req.getReqList()
                .stream()
                // 获取缓存中，如果缓存中有，说明要刷新；不在，则跳过
                .map(a -> batch.containsKey(a.getUid()) ? batch.get(a.getUid()) : SummeryInfoDTO.skip(a.getUid()))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    /**
     * 获取徽章聚合信息
     * @param req
     * @return
     */
    @Override
    public List<ItemInfoDTO> getItemInfo(ItemInfoReq req) {
        // 1.获取物品聚合信息请求集合（包含id、最后更新时间）
        List<ItemInfoReq.infoReq> reqList = req.getReqList();
        // 2.处理
        List<ItemInfoDTO> itemInfoDTOList = reqList.stream().map(a -> {
            ItemConfig itemConfig = itemCache.getById(a.getItemId());
            // 判断是否需要更新
            if (ObjUtil.isNotNull(a.getLastModifyTime()) && itemConfig.getUpdateTime().getTime() <= a.getLastModifyTime()) {
                // 跳过
                return ItemInfoDTO.skip(a.getItemId());
            }
            // 组装ItemInfoDTO，是获取缓存itemConfig里的
            ItemInfoDTO itemInfoDTO = new ItemInfoDTO();
            itemInfoDTO.setItemId(itemConfig.getId());
            itemInfoDTO.setImg(itemConfig.getImg());
            itemInfoDTO.setDescribe(itemConfig.getDescribe());
            return itemInfoDTO;
        }).collect(Collectors.toList());
        return itemInfoDTOList;
    }

    /**
     * 获取全员群成员列表（前1000未）
     * @return
     */
    @Override
    public List<User> getMemberList() {
        return lambdaQuery()
                .eq(User::getStatus, NormalOrNoEnum.NORMAL.getStatus())// 获取正常用户
                .orderByDesc(User::getLastOptTime)// 按最后活跃时间排序
                .last("limit 1000")// 获取前1000个
                .select(User::getId,User::getName,User::getAvatar)// 获取id、名字、头像
                .list();
    }

    /**
     * 获取需要同步的用户id集合
     * @param reqList
     * @return
     */
    private List<Long> getNeedSyncUserIdList(List<SummeryInfoReq.infoReq> reqList) {
        // 1.创建返回集合
        List<Long> needSyncUidList = new ArrayList<>();
        // 2.从缓存中根据uid获取的最后一次更新信息
        // 获取uid集合
        List<Long> uidList = reqList.stream().map(SummeryInfoReq.infoReq::getUid).collect(Collectors.toList());
        List<Long> userModifyTime = userCache.getUserModifyTime(uidList);
        // 3.遍历，判断是否需要同步
        for (int i = 0; i < reqList.size(); i++) {
            // 获取当前用户的最后一次更新时间
            SummeryInfoReq.infoReq req = reqList.get(i);
            // Redis 中获取的修改时间
            Long modifyTime = userModifyTime.get(i);
            if (Objects.isNull(req.getLastModifyTime()) || (Objects.nonNull(modifyTime) && modifyTime > req.getLastModifyTime())){
                // 需要同步
                needSyncUidList.add(req.getUid());
            }
        }
        return needSyncUidList;
    }

    /**
     * 拉黑 ip 的方式
     * @param ipInfo
     * @return
     */
    private void blackIP(IpInfo ipInfo){
        if (ipInfo == null) return;
        // 1.构建 ip 黑名单对象
        Black blackCreateIP = new Black();
        blackCreateIP.setType(BlackTypeEnum.IP.getType());
        blackCreateIP.setTarget(ipInfo.getCreateIp());
        // 防止两个ip相同，重复拉黑
        if (!ipInfo.getCreateIpDetail().equals(ipInfo.getUpdateIpDetail())){
            Black blackUpdateIp = new Black();
            blackUpdateIp.setType(BlackTypeEnum.IP.getType());
            blackUpdateIp.setTarget(ipInfo.getUpdateIp());
            blackService.save(blackUpdateIp);
        }
        // 2.拉黑用户，ip 的方式
        blackService.save(blackCreateIP);
    }
}